﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Configuration.Provider;
using System.Data.Entity;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Web.Profile;
using System.Xml.Serialization;
using Tiscs.Practices.Diagnostics;
using Tiscs.Practices.Web.Providers.Entities;

namespace Tiscs.Practices.Web.Providers
{
    /// <summary>
    /// 用户配置提供程序。
    /// </summary>
    public abstract class DefaultProfileProvider : ProfileProvider
    {
        #region Abstract Methods

        /// <summary>
        /// 创建DbContext实例。
        /// </summary>
        /// <returns>DbContext实例。</returns>
        protected abstract DbContext CreateDbContext();

        #endregion

        #region Static Methods

        private static void ParseDataFromDB(string[] names, string values, SettingsPropertyValueCollection settings)
        {
            if (names == null || values == null || settings == null)
            {
                return;
            }
            try
            {
                for (int i = 0; i < names.Length / 3; i++)
                {
                    int num = i * 3;
                    string name = names[num];
                    SettingsPropertyValue setting = settings[name];
                    if (setting != null)
                    {
                        int num2 = int.Parse(names[num + 1], CultureInfo.InvariantCulture);
                        int num3 = int.Parse(names[num + 2], CultureInfo.InvariantCulture);
                        if (num3 == -1 && !setting.Property.PropertyType.IsValueType)
                        {
                            setting.PropertyValue = null;
                            setting.IsDirty = false;
                            setting.Deserialized = true;
                        }
                        else
                        {
                            if (num2 >= 0 && num3 > 0 && values.Length >= num2 + num3)
                            {
                                setting.PropertyValue = DefaultProfileProvider.DeserializeFromString(setting.Property, values.Substring(num2, num3));
                                setting.IsDirty = false;
                                setting.Deserialized = true;
                            }
                        }
                    }
                }
            }
            catch (Exception)
            {
            }
        }

        private static string SerializeToString(SettingsPropertyValue value)
        {
            if (value.PropertyValue == null)
            {
                return null;
            }
            SettingsSerializeAs settingsSerializeAs = value.Property.SerializeAs;
            Type propertyType = value.Property.PropertyType;
            if (settingsSerializeAs == SettingsSerializeAs.ProviderSpecific)
            {
                if (propertyType == typeof(string) || propertyType.IsPrimitive)
                {
                    settingsSerializeAs = SettingsSerializeAs.String;
                }
                else
                {
                    settingsSerializeAs = SettingsSerializeAs.Xml;
                }
            }
            switch (settingsSerializeAs)
            {
                case SettingsSerializeAs.String:
                    {
                        TypeConverter converter = TypeDescriptor.GetConverter(propertyType);
                        if (converter != null && converter.CanConvertTo(typeof(string)) && converter.CanConvertFrom(typeof(string)))
                        {
                            return converter.ConvertToInvariantString(value.PropertyValue);
                        }
                        else
                        {
                            throw new ProviderException(string.Format(ProvidersHelper.Unable_To_Convert_Type_To_String, propertyType.ToString()));
                        }
                    }
                case SettingsSerializeAs.Xml:
                    {
                        string result;
                        using (StringWriter stringWriter = new StringWriter())
                        {
                            XmlSerializer xmlSerializer = new XmlSerializer(propertyType);
                            xmlSerializer.Serialize(stringWriter, value.PropertyValue);
                            result = stringWriter.ToString();
                        }
                        return result;
                    }
                case SettingsSerializeAs.Binary:
                    {
                        MemoryStream memoryStream = new MemoryStream();
                        try
                        {
                            NetDataContractSerializer netDataContractSerializer = new NetDataContractSerializer();
                            netDataContractSerializer.Serialize(memoryStream, value.PropertyValue);
                            string result = Convert.ToBase64String(memoryStream.ToArray());
                            return result;
                        }
                        finally
                        {
                            memoryStream.Close();
                        }
                    }
                default:
                    return null;
            }
        }

        private static object DeserializeFromString(SettingsProperty property, string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return null;
            }
            Type propertyType = property.PropertyType;
            SettingsSerializeAs serializeAs = property.SerializeAs;
            if (propertyType == typeof(string) && serializeAs == SettingsSerializeAs.String)
            {
                return value;
            }
            switch (serializeAs)
            {
                case SettingsSerializeAs.String:
                    {
                        TypeConverter converter = TypeDescriptor.GetConverter(propertyType);
                        if ((converter == null || !converter.CanConvertTo(typeof(string))) || !converter.CanConvertFrom(typeof(string)))
                        {
                            throw new ProviderException(string.Format(ProvidersHelper.Unable_To_Convert_Type_To_String, propertyType.ToString()));
                        }
                        return converter.ConvertFromInvariantString(value);
                    }
                case SettingsSerializeAs.Xml:
                    {
                        using (StringReader stringReader = new StringReader(value))
                        {
                            XmlSerializer xmlSerializer = new XmlSerializer(propertyType);
                            object result = xmlSerializer.Deserialize(stringReader);
                            return result;
                        }
                    }
                case SettingsSerializeAs.Binary:
                    {
                        byte[] buffer = Convert.FromBase64String(value);
                        MemoryStream memoryStream = null;
                        try
                        {
                            memoryStream = new MemoryStream(buffer);
                            object result = new NetDataContractSerializer().Deserialize(memoryStream);
                            return result;
                        }
                        finally
                        {
                            if (memoryStream != null)
                            {
                                memoryStream.Close();
                            }
                        }
                    }
                default:
                    return null;
            }
        }

        private static void PrepareDataForSaving(ref string names, ref string values, SettingsPropertyValueCollection settings, bool authenticated)
        {
            StringBuilder namesBuilder = new StringBuilder();
            StringBuilder valuesBuilder = new StringBuilder();
            bool flag = false;
            foreach (SettingsPropertyValue setting in settings)
            {
                if (setting.IsDirty && (authenticated || (bool)setting.Property.Attributes["AllowAnonymous"]))
                {
                    flag = true;
                    break;
                }
            }
            if (!flag)
            {
                return;
            }
            foreach (SettingsPropertyValue setting in settings)
            {
                if (!authenticated)
                {
                    bool allowAnonymous = (bool)setting.Property.Attributes["AllowAnonymous"];
                    if (!allowAnonymous)
                    {
                        continue;
                    }
                }
                if (setting.IsDirty || !setting.UsingDefaultValue)
                {
                    int num = 0;
                    string text = null;
                    int num2;
                    if (setting.Deserialized && setting.PropertyValue == null)
                    {
                        num2 = -1;
                    }
                    else
                    {
                        text = DefaultProfileProvider.SerializeToString(setting);
                        num2 = text.Length;
                        num = valuesBuilder.Length;
                    }
                    namesBuilder.Append(string.Concat(new string[] { setting.Name, ":", num.ToString(CultureInfo.InvariantCulture), ":", num2.ToString(CultureInfo.InvariantCulture), ":" }));
                    if (text != null)
                    {
                        valuesBuilder.Append(text);
                    }
                }
            }
            names = namesBuilder.ToString();
            values = valuesBuilder.ToString();
        }

        #endregion

        #region ProfileProvider

        /// <summary>
        /// 应用程序名称。
        /// </summary>
        public override string ApplicationName { get; set; }

        /// <summary>
        /// 初始化提供程序。
        /// </summary>
        /// <param name="name">会话状态提供程序的友好名称。</param>
        /// <param name="config">会话状态提供程序初始化配置。</param>
        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config");
            }
            if (string.IsNullOrEmpty(name))
            {
                name = "DefaultProfileProvider";
            }
            base.Initialize(name, config);
            if (!string.IsNullOrEmpty(config["applicationName"]))
            {
                this.ApplicationName = config["applicationName"];
            }
            else
            {
                this.ApplicationName = ProvidersHelper.GetDefaultApplicationName();
            }
            config.Remove("applicationName");
            if (config.Count > 0)
            {
                throw new ProviderException(string.Format(ProvidersHelper.Unrecognized_Attribute, config.GetKey(0)));
            }
        }

        /// <summary>
        /// 删除不活动的用户配置。
        /// </summary>
        /// <param name="authenticationOption">检索用户配置的范围。</param>
        /// <param name="userInactiveSinceDate">用户配置过期时间。</param>
        /// <returns>删除的配置文件的数目。</returns>
        public override int DeleteInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            int result = 0;
            using (DbContext db = CreateDbContext())
            {
                IQueryable<ProfileEntity> inactiveProfiles = db.GetInactiveProfiles(ApplicationName, authenticationOption, userInactiveSinceDate);
                foreach (ProfileEntity current in inactiveProfiles)
                {
                    ProfileEntity profile = db.GetProfiles().Find(current.UserId);
                    db.GetProfiles().Remove(profile);
                    result++;
                }
                db.SaveChanges();
            }
            return result;
        }

        /// <summary>
        /// 删除用户配置文件。
        /// </summary>
        /// <param name="userNames">要删除的配置文件的用户名字符串数组。</param>
        /// <returns>删除的配置文件的数目。</returns>
        public override int DeleteProfiles(string[] userNames)
        {
            Exception exception = Validation.Parameter<string[]>("userNames").AssertNotNullOrEmpty().Validate(userNames);
            if (exception != null)
            {
                throw exception;
            }
            List<string> list = new List<string>();
            for (int i = 0; i < userNames.Length; i++)
            {
                string userName = userNames[i];
                exception = Validation.Parameter<string>("userNames[" + i + "]").AssertNotNull().AssertMaxLength(UserEntity.UserNameMaxLength).AssertNoCommas().TrimAndValidate(ref userName);
                if (exception != null)
                {
                    throw exception;
                }
                if (list.Contains(userName))
                {
                    throw new ArgumentException(string.Format(ProvidersHelper.Parameter_Duplicate_Array_Element, "userNames"), "userNames");
                }
                list.Add(userName);
            }
            int result = 0;
            using (DbContext db = CreateDbContext())
            {
                foreach (string userName in list)
                {
                    ProfileEntity profile = db.GetProfile(ApplicationName, userName);
                    if (profile != null)
                    {
                        result++;
                        db.GetProfiles().Remove(profile);
                    }
                    db.SaveChanges();
                }
            }
            return result;
        }

        /// <summary>
        /// 删除用户配置文件。
        /// </summary>
        /// <param name="profiles">要删除的用户配置文件。</param>
        /// <returns>删除的配置文件的数目。</returns>
        public override int DeleteProfiles(ProfileInfoCollection profiles)
        {
            Exception exception = Validation.Parameter<ProfileInfoCollection>("profiles").AssertNotNullOrEmpty().Validate(profiles);
            if (exception != null)
            {
                throw exception;
            }
            int result = 0;
            using (DbContext db = CreateDbContext())
            {
                foreach (ProfileInfo profileInfo in profiles)
                {
                    ProfileEntity profile = db.GetProfile(ApplicationName, profileInfo.UserName);
                    if (profile != null)
                    {
                        result++;
                        db.GetProfiles().Remove(profile);
                    }
                }
                db.SaveChanges();
            }
            return result;
        }

        /// <summary>
        /// 按用户名匹配匹配检索不活动的用户配置。
        /// </summary>
        /// <param name="authenticationOption">检索用户配置的范围。</param>
        /// <param name="userNameToMatch">用于匹配检索的用户名。</param>
        /// <param name="userInactiveSinceDate">用户配置过期时间。</param>
        /// <param name="pageIndex">返回的分页数据页索引。</param>
        /// <param name="pageSize">返回的分页数据页大小。</param>
        /// <param name="totalRecords">检索到的数据总条数。</param>
        /// <returns>检索到的用户配置的分页数据。</returns>
        public override ProfileInfoCollection FindInactiveProfilesByUserName(ProfileAuthenticationOption authenticationOption, string userNameToMatch, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            Exception exception = Validation.Parameter<string>("userNameToMatch").AssertNotNullOrEmpty().AssertMaxLength(UserEntity.UserNameMaxLength).TrimAndValidate(ref userNameToMatch);
            if (exception != null)
            {
                throw exception;
            }
            ProfileInfoCollection profiles = new ProfileInfoCollection();
            using (DbContext db = CreateDbContext())
            {
                IQueryable<ProfileEntity> queryable = db.GetProfiles(ApplicationName, authenticationOption, userInactiveSinceDate, userNameToMatch);
                totalRecords = queryable.Count();
                if (pageIndex != -1 && pageSize != -1)
                {
                    queryable = queryable.Skip(pageIndex * pageSize).Take(pageSize);
                }
                foreach (ProfileEntity entity in queryable)
                {
                    profiles.Add(entity.ToProfileInfo());
                }
            }
            return profiles;
        }

        /// <summary>
        /// 按用户名匹配匹配检索用户配置。
        /// </summary>
        /// <param name="authenticationOption">检索用户配置的范围。</param>
        /// <param name="userNameToMatch">用于匹配检索的用户名。</param>
        /// <param name="pageIndex">返回的分页数据页索引。</param>
        /// <param name="pageSize">返回的分页数据页大小。</param>
        /// <param name="totalRecords">检索到的数据总条数。</param>
        /// <returns>检索到的用户配置的分页数据。</returns>
        public override ProfileInfoCollection FindProfilesByUserName(ProfileAuthenticationOption authenticationOption, string userNameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            Exception exception = Validation.Parameter<string>("userNameToMatch").AssertNotNullOrEmpty().AssertMaxLength(UserEntity.UserNameMaxLength).TrimAndValidate(ref userNameToMatch);
            if (exception != null)
            {
                throw exception;
            }
            ProfileInfoCollection profiles = new ProfileInfoCollection();
            using (DbContext db = CreateDbContext())
            {
                IQueryable<ProfileEntity> queryable = db.GetProfiles(ApplicationName, authenticationOption, DateTime.MaxValue, userNameToMatch);
                totalRecords = queryable.Count();
                if (pageIndex >= 0 && pageSize >= 0)
                {
                    queryable = queryable.Skip(pageIndex * pageSize).Take(pageSize);
                }
                foreach (ProfileEntity entity in queryable)
                {
                    profiles.Add(entity.ToProfileInfo());
                }
            }
            return profiles;
        }

        /// <summary>
        /// 检索所有不活动的用户配置。
        /// </summary>
        /// <param name="authenticationOption">检索用户配置的范围。</param>
        /// <param name="userInactiveSinceDate">用户配置过期时间。</param>
        /// <param name="pageIndex">返回的分页数据页索引。</param>
        /// <param name="pageSize">返回的分页数据页大小。</param>
        /// <param name="totalRecords">检索到的数据总条数。</param>
        /// <returns>检索到的用户配置的分页数据。</returns>
        public override ProfileInfoCollection GetAllInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate, int pageIndex, int pageSize, out int totalRecords)
        {
            ProfileInfoCollection profiles = new ProfileInfoCollection();
            using (DbContext db = CreateDbContext())
            {
                IQueryable<ProfileEntity> queryable = db.GetProfiles(ApplicationName, authenticationOption, userInactiveSinceDate, null);
                totalRecords = queryable.Count();
                if (pageIndex >= 0 && pageSize >= 0)
                {
                    queryable = queryable.Skip(pageIndex * pageSize).Take(pageSize);
                }
                foreach (ProfileEntity entity in queryable)
                {
                    profiles.Add(entity.ToProfileInfo());
                }
            }
            return profiles;
        }

        /// <summary>
        /// 检索所有用户配置。
        /// </summary>
        /// <param name="authenticationOption">检索用户配置的范围。</param>
        /// <param name="pageIndex">返回的分页数据页索引。</param>
        /// <param name="pageSize">返回的分页数据页大小。</param>
        /// <param name="totalRecords">检索到的数据总条数。</param>
        /// <returns>检索到的用户配置的分页数据。</returns>
        public override ProfileInfoCollection GetAllProfiles(ProfileAuthenticationOption authenticationOption, int pageIndex, int pageSize, out int totalRecords)
        {
            ProfileInfoCollection profiles = new ProfileInfoCollection();
            using (DbContext db = CreateDbContext())
            {
                IQueryable<ProfileEntity> queryable = db.GetProfiles(ApplicationName, authenticationOption, DateTime.MaxValue, null);
                totalRecords = queryable.Count();
                if (pageIndex >= 0 && pageSize >= 0)
                {
                    queryable = queryable.Skip(pageIndex * pageSize).Take(pageSize);
                }
                foreach (ProfileEntity entity in queryable)
                {
                    profiles.Add(entity.ToProfileInfo());
                }
            }
            return profiles;
        }

        /// <summary>
        /// 获取不活动的用户配置文件条数。
        /// </summary>
        /// <param name="authenticationOption">检索用户配置的范围。</param>
        /// <param name="userInactiveSinceDate">用户配置过期时间。</param>
        /// <returns>不活动的用户配置文件条数。</returns>
        public override int GetNumberOfInactiveProfiles(ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
        {
            int result;
            using (DbContext db = CreateDbContext())
            {
                result = db.GetProfiles(ApplicationName, authenticationOption, DateTime.MaxValue, null).Count();
            }
            return result;
        }

        /// <summary>
        /// 获取用户配置属性名和属性值集合。
        /// </summary>
        /// <param name="context">当前配置上下文。</param>
        /// <param name="collection">属性名称集合。</param>
        /// <returns>用户配置属性名和属性值集合。</returns>
        public override SettingsPropertyValueCollection GetPropertyValues(SettingsContext context, SettingsPropertyCollection collection)
        {
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            SettingsPropertyValueCollection settingsPropertyValueCollection = new SettingsPropertyValueCollection();
            if (collection.Count >= 1)
            {
                string userName = (string)context["UserName"];
                foreach (SettingsProperty settingsProperty in collection)
                {
                    if (settingsProperty.SerializeAs == SettingsSerializeAs.ProviderSpecific)
                    {
                        if (settingsProperty.PropertyType.IsPrimitive || settingsProperty.PropertyType == typeof(string))
                        {
                            settingsProperty.SerializeAs = SettingsSerializeAs.String;
                        }
                        else
                        {
                            settingsProperty.SerializeAs = SettingsSerializeAs.Xml;
                        }
                    }
                    settingsPropertyValueCollection.Add(new SettingsPropertyValue(settingsProperty));
                }
                if (!string.IsNullOrEmpty(userName))
                {
                    using (DbContext db = CreateDbContext())
                    {
                        ProfileEntity profile = db.GetProfile(this.ApplicationName, userName);
                        if (profile != null)
                        {
                            DefaultProfileProvider.ParseDataFromDB(profile.PropertyNames.Split(':'), profile.PropertyValueStrings, settingsPropertyValueCollection);
                        }
                    }
                }
            }
            return settingsPropertyValueCollection;
        }

        /// <summary>
        /// 设置用户配置属性名和属性值集合。
        /// </summary>
        /// <param name="context">当前配置上下文。</param>
        /// <param name="collection">用户配置属性名和属性值集合。</param>
        public override void SetPropertyValues(SettingsContext context, SettingsPropertyValueCollection collection)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }
            if (collection == null)
            {
                throw new ArgumentNullException("collection");
            }
            string userName = (string)context["UserName"];
            bool authenticated = (bool)context["IsAuthenticated"];
            if (!string.IsNullOrEmpty(userName) && collection.Count > 0)
            {
                string names = string.Empty;
                string values = string.Empty;
                DefaultProfileProvider.PrepareDataForSaving(ref names, ref values, collection, authenticated);
                if (names.Length == 0)
                {
                    return;
                }
                using (DbContext db = CreateDbContext())
                {
                    ProfileEntity profile = db.GetProfile(ApplicationName, userName);
                    if (profile == null)
                    {
                        ApplicationEntity application = db.GetApplication(ApplicationName, true);
                        UserEntity user = db.GetUser(ApplicationName, userName);
                        if (user == null)
                        {
                            user = new UserEntity()
                            {
                                ApplicationId = application.ApplicationId,
                                UserId = Guid.NewGuid(),
                                UserName = userName,
                                IsAnonymous = !authenticated
                            };
                            db.GetUsers().Add(user);
                        }
                        profile = new ProfileEntity();
                        profile.UserId = user.UserId;
                        profile.User = user;
                        db.GetProfiles().Add(profile);
                    }
                    profile.User.LastActivityDate = DateTime.UtcNow;
                    profile.LastUpdatedDate = profile.User.LastActivityDate;
                    profile.PropertyNames = names;
                    profile.PropertyValueStrings = values;
                    profile.PropertyValueBinary = new byte[0];
                    db.SaveChanges();
                }
            }
        }

        #endregion
    }
}
